package com.greatwqs.wmall.order.provider.service.impl;

import com.google.common.collect.Lists;
import com.greatwqs.wmall.common.module.exception.AppException;
import com.greatwqs.wmall.common.module.exception.ErrorCode;
import com.greatwqs.wmall.common.module.util.JsonUtil;
import com.greatwqs.wmall.order.client.enums.OrderStatus;
import com.greatwqs.wmall.order.client.enums.OrderType;
import com.greatwqs.wmall.order.provider.mapper.OrderDetailMapper;
import com.greatwqs.wmall.order.provider.mapper.OrderMapper;
import com.greatwqs.wmall.order.provider.model.dto.OrderCreateDto;
import com.greatwqs.wmall.order.provider.model.po.Order;
import com.greatwqs.wmall.order.provider.model.po.OrderDetail;
import com.greatwqs.wmall.order.provider.service.OrderService;
import com.greatwqs.wmall.order.provider.util.OrderUtils;
import com.greatwqs.wmall.product.client.model.ProductResponse;
import com.greatwqs.wmall.product.client.model.ProductSubStockRequest;
import com.greatwqs.wmall.product.client.service.ProductClientService;

import lombok.extern.slf4j.Slf4j;

import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/***
 * 订单服务
 * @author wang-qingsong
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @DubboReference
    private ProductClientService productClientService;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    /**
     * 创建订单
     */
    @Override
    @GlobalTransactional(timeoutMills = 60000, name = "create-order-tx")
    public Long createOrder(OrderCreateDto orderCreateDto) {
        log.info("createOrder orderCreateDto: {}", JsonUtil.toJson(orderCreateDto));
        // 1. 创建订单基本数据
        Order order = buildOrderPo(orderCreateDto);
        log.info("createOrder buildOrderPo, order: {}", JsonUtil.toJson(order));

        // 2. 调用商品 Dubbo 服务，扣减库存
        updateSubProductStock(orderCreateDto.getProducts());
        log.info("createOrder updateSubProductStock, products: {}", JsonUtil.toJson(orderCreateDto.getProducts()));

        // 3. 保存订单
        orderMapper.insert(order);
        Long orderId = order.getOrderId();
        List<OrderDetail> orderDetailList = order.getOrderDetailList();
        for (OrderDetail orderDetail : orderDetailList) {
            orderDetail.setOrderId(orderId);
            orderDetailMapper.insert(orderDetail);
        }
        log.info("createOrder save order, orderId: {}", orderId);

        /**
         * 测试事务，1/3 概率创建订单失败（但是会扣库存）
         */
        if (orderCreateDto.getUserId() == 7L && System.currentTimeMillis() % 3 == 0) {
            log.error("createOrder FAIL, orderId: {}", orderId);
            throw new AppException(ErrorCode.ORDER_CREATE_FAIL);
        }
        return orderId;
    }

    /**
     * 调用 Dubbo 商品服务，扣减库存
     */
    private void updateSubProductStock(List<OrderCreateDto.OrderProductDto> productDtoList) throws AppException {
        List<ProductSubStockRequest> subStockRequestList = productDtoList.stream().map(productDto -> new ProductSubStockRequest(productDto.getProductId(), productDto.getProductNum())).collect(Collectors.toList());
        productClientService.subProductStock(subStockRequestList);
    }

    private Order buildOrderPo(OrderCreateDto dto) {
        Order order = new Order();
        order.setOrderId(null);
        order.setOrderNo(OrderUtils.getOrderNo(OrderType.NORMAL));
        order.setOrderDes("电商普通订单");
        order.setOrderType(OrderType.NORMAL.getValue());
        order.setOrderStatus(OrderStatus.CREATED.getValue());
        order.setUserId(dto.getUserId());
        order.setValid(true);
        order.setCreateTime(new Date());
        order.setUpdateTime(new Date());

        // 订单详情 & 计算订单价格
        List<OrderDetail> orderDetailList = buildOrderDetailPos(dto);
        order.setOrderPrice(calculateOrderPrice(orderDetailList));
        order.setOrderDetailList(orderDetailList);
        return order;
    }

    private static BigDecimal calculateOrderPrice(List<OrderDetail> orderDetailList) {
        BigDecimal total = new BigDecimal(0);
        for (OrderDetail orderDetail : orderDetailList) {
            total = total.add(orderDetail.getDetailPrice());
        }
        return total;
    }

    private List<OrderDetail> buildOrderDetailPos(OrderCreateDto dto) {
        List<OrderDetail> orderDetails = Lists.newArrayList();
        List<Long> productIds = dto.getProducts().stream().map(OrderCreateDto.OrderProductDto::getProductId).toList();
        Map<Long, ProductResponse> productResponseMap = productClientService.getProductMapByIds(productIds);
        for (OrderCreateDto.OrderProductDto productDto : dto.getProducts()) {
            ProductResponse product = productResponseMap.get(productDto.getProductId());
            OrderDetail orderDetail = buildOrderDetailPo(productDto, product);
            orderDetails.add(orderDetail);
        }
        return orderDetails;
    }

    private OrderDetail buildOrderDetailPo(OrderCreateDto.OrderProductDto productDto, ProductResponse product) {
        if (product == null) {
            log.warn("buildOrderDetailPo product is null, productId: {}", productDto.getProductId());
            throw new AppException(ErrorCode.PRODUCT_NOT_FOUND);
        }

        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setOrderDetailId(null);
        orderDetail.setOrderId(null);
        orderDetail.setProductId(product.getProductId());
        orderDetail.setProductPrice(product.getPrice());
        orderDetail.setProductNum(productDto.getProductNum());
        orderDetail.setProductName(product.getProductName());
        orderDetail.setProductImg(product.getProductImg());
        orderDetail.setValid(true);
        orderDetail.setCreateTime(new Date());
        orderDetail.setUpdateTime(new Date());
        return orderDetail;
    }

}
